/*
 * Copyright (c) zhg2yqq.com Corp.
 * All Rights Reserved.
 */
package com.zhg2yqq.wheels.security;

import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;

import com.zhg2yqq.wheels.cache.service.ICacheService;
import com.zhg2yqq.wheels.common.exception.BaseException;
import com.zhg2yqq.wheels.security.constants.SecurityCacheConstant;
import com.zhg2yqq.wheels.security.context.SecurityLoginUser;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.exceptions.ValidateException;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import cn.hutool.jwt.JWT;
import cn.hutool.jwt.JWTValidator;
import cn.hutool.jwt.RegisteredPayload;

/**
 * @author zhg2yqq, 2023年3月17日
 * @version zhg2yqq v1.0
 */
public class SecurityRepositoryHelper implements ISecurityRepository {
    private final Logger log = LoggerFactory.getLogger(SecurityRepositoryHelper.class);
    private final CustomeSecurityProperties securityProperties;
    public static final String HEADER_TOKEN_PREFIX = "Bearer ";
    private ICacheService cacheService;
    private ISecurityEventHook securityEventHook;

    public SecurityRepositoryHelper(ICacheService cacheService,
                                    ISecurityEventHook securityEventHook,
                                    CustomeSecurityProperties securityProperties) {
        this.cacheService = cacheService;
        this.securityEventHook = securityEventHook;
        this.securityProperties = securityProperties;
    }

    @Override
    public void clearLoginUser(HttpServletRequest request) {
        String token = getAuthorization(request);
        if (token == null) {
            return;
        }
        this.clearLoginUser(token);
    }

    @Override
    public void clearLoginUser(String token) {
        Authentication authentication = getAuthenticationFromJwt(token);
        if (authentication != null) {
            JWT jwt = JWT.of(token).setKey(securityProperties.getTokenSecret().getBytes());
            Date expiresDate = jwt.getPayload().getClaimsJson().getDate(RegisteredPayload.EXPIRES_AT);
            int ttl = expiresDate == null ? 60 * 60 * 24 * 7
                    : (int) (DateUtil.second(expiresDate) - DateUtil.currentSeconds());
            cacheService.saveTopicCacheWithTtl(SecurityCacheConstant.USELESS_USER_SECURITY,
                    token, 1, Math.max(0, ttl));
        }
        securityEventHook.catchClearLoginUser(token, authentication);
    }

    @Override
    public Authentication getLoginUser(HttpServletRequest request) {
        String token = getAuthorization(request);
        return getAuthenticationFromJwt(token);
    }

    private Authentication getAuthenticationFromJwt(String jwtStr) {
        if (jwtStr == null) {
            return null;
        }
        JWT jwt = JWT.of(jwtStr).setKey(securityProperties.getTokenSecret().getBytes());
        if (!jwt.verify()) {
            // 无效认证
//            throw new BadCredentialsException(SecurityResponseCode.SECURITY_INVALID_TOKEN.getDescription());
            return null;
        }
        try {
            JWTValidator.of(jwt).validateDate();
        } catch (ValidateException e) {
            // 过期认证
//            throw new CredentialsExpiredException(SecurityResponseCode.SECURITY_IS_TIMEOUT.getDescription());
            return null;
        }
        Object useless = cacheService.getTopicValue(SecurityCacheConstant.USELESS_USER_SECURITY,
                jwtStr);
        if (useless != null) {
            // 已登出失效的认证
//            throw new BadCredentialsException(SecurityResponseCode.SECURITY_IS_TIMEOUT.getDescription());
            return null;
        }

        UsernamePasswordAuthenticationToken authentication = null;
        Object obj = jwt.getPayload("user");
        if (obj instanceof JSONObject) {
            SecurityLoginUser loginUser = JSONUtil.toBean((JSONObject) obj,
                    SecurityLoginUser.class);
            loginUser.setAccessToken(jwtStr);

            authentication = UsernamePasswordAuthenticationToken.authenticated(loginUser, null,
                    loginUser.getAuthorities());
        }
        return authentication;
    }

    @Override
    public String saveLoginUser(HttpServletRequest request, HttpServletResponse response,
                                Authentication authentication) {
        if (authentication.getPrincipal() instanceof SecurityLoginUser) {
            SecurityLoginUser securityLoginUser = (SecurityLoginUser) authentication.getPrincipal();
            long customExpireTime = securityLoginUser.getSecurityTtl() == null ? securityProperties.getTokenExpireTime()
                    : securityLoginUser.getSecurityTtl();
            // 生成jwt token
            Map<String, Object> payload = new HashMap<>();
            Date date = new Date(System.currentTimeMillis() + customExpireTime);
            if (securityLoginUser.getLoginPlatform() != null) {
                payload.put("loginPlatform", securityLoginUser.getLoginPlatform());
            }
            payload.put("user", securityLoginUser);
            String token = JWT.create().setAudience(securityLoginUser.getUsername())
                    .setKey(securityProperties.getTokenSecret().getBytes())
                    .setExpiresAt(date).setJWTId(securityLoginUser.getAccessKey())
                    .addPayloads(payload).sign();
            securityLoginUser.setAccessToken(token);
            // 持久化登录信息
            int ttl = (int) TimeUnit.MILLISECONDS.toSeconds(customExpireTime);
            securityEventHook.catchSaveLoginUser(token, authentication, ttl, request, response);
            return token;
        }

        return null;
    }

    /**
     * 创建验证码
     *
     * @return
     */
    @Override
    public void saveCaptcha(String uuid, String code, int ttl) {
        cacheService.saveTopicCacheWithTtl(SecurityCacheConstant.LOGIN_CAPTCHA, uuid, code, ttl);
    }

    /**
     * 获取缓存验证码
     *
     * @return
     */
    @Override
    public String getCaptcha(String uuid) {
        return (String) cacheService.getTopicValue(SecurityCacheConstant.LOGIN_CAPTCHA, uuid);
    }

    /**
     * 获取缓存验证码
     *
     * @return
     */
    @Override
    public void clearCaptcha(String uuid) {
        cacheService.removeTopicCache(SecurityCacheConstant.LOGIN_CAPTCHA, uuid);
    }

    private String getAuthorization(HttpServletRequest request) {
        String tokenName = securityProperties.getTokenName();
        String token = request.getHeader(tokenName);
        if (CharSequenceUtil.isNotEmpty(token)) {
            return CharSequenceUtil.removePrefix(token, HEADER_TOKEN_PREFIX);
        }
        Cookie[] cookies = request.getCookies();
        if (cookies != null) {
            for (Cookie cookie : cookies) {
                if (tokenName.equals(cookie.getName())) {
                    try {
                        return URLDecoder.decode(cookie.getValue(),
                                StandardCharsets.UTF_8.toString());
                    } catch (UnsupportedEncodingException | BaseException e) {
                    }
                }
            }
        }
        return null;
    }
}
